/*
 * DefaultTreeModel.java  0.4.0 / Oct 25, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing.tree;

import com.cute4j.core.events.EventListenerList;
import com.cute4j.swing.events.TreeModelEvent;
import com.cute4j.swing.events.TreeModelListener;


/**
 * Default Tree Model.
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public class DefaultTreeModel implements TreeModel {

// Public Static Fields. -------------------------------------------------------

  /**
   * Empty array of tree nodes (the value is TreeNode[0]).
   */
  public static final TreeNode[] EMPTY_TREE_NODE_ARRAY = new TreeNode[0];

// Protected Fields. -----------------------------------------------------------

  protected TreeNode root;
  protected boolean asksAllowsChildren;
  protected final EventListenerList listenerList;

// Public Constructors. --------------------------------------------------------

  /**
   * Constructs new DefaultTreeModel object.
   *
   * @param root the root
   */
  public DefaultTreeModel(TreeNode root) {
    this(root, false);
  }

  /**
   * Constructs new DefaultTreeModel object.
   *
   * @param root the root
   * @param asksAllowsChildren <tt>true</tt> if leaf nodes are determined
   */
  public DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) {
    this.root = root;
    this.asksAllowsChildren = asksAllowsChildren;
    listenerList = new EventListenerList();
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Sets root node.
   *
   * @param root the root node
   */
  public void setRoot(TreeNode root) {
    Object oldRoot = this.root;
    if (oldRoot != root) { // NOPMD
      this.root = root;
      if ((root == null) && (oldRoot != null)) {
        fireTreeStructureChanged(this, null);
      } else {
        nodeStructureChanged(root);
      }
    }
  }

  /**
   * Returns <tt>true</tt> if leaf nodes are determined.
   *
   * @return <tt>true</tt> if leaf nodes are determined
   */
  public boolean asksAllowsChildren() {
    return asksAllowsChildren;
  }

  /**
   * Sets whether or not the leaf nodes are determined.
   *
   * @param asksAllowsChildren <tt>true</tt> if determined
   */
  public void setAsksAllowsChildren(boolean asksAllowsChildren) {
    this.asksAllowsChildren = asksAllowsChildren;
  }

  /**
   * Returns tree model listeners.
   *
   * @return tree model listeners
   */
  public TreeModelListener[] getTreeModelListeners() {
    int count = listenerList.getListenerCount(TreeModelListener.class);
    TreeModelListener[] listeners = new TreeModelListener[count];
    if (count > 0) {
      System.arraycopy(listenerList.getListenersArray(TreeModelListener.class),
                       0, listeners,  0, count);
    }
    return listeners;
  }

  /**
   * Called when node structure has changed.
   *
   * @param node the node
   */
  public void nodeStructureChanged(TreeNode node) {
    if (node != null) {
      fireTreeStructureChanged(this, getPathToRoot(node), null, null);
    }
  }

  /**
    * Called when node has changed.
    *
    * @param node the node
    */
  public void nodeChanged(TreeNode node) {
    if (listenerList != null && node != null) {
      TreeNode parent = node.getParent();
      if (parent == null) {
        nodesChanged(node, null);
      } else if (node == getRoot()) {
        int index = parent.getIndex(node);
        if (index != -1) {
          nodesChanged(parent, new int[] {index});
        }
      }
    }
  }

   /**
     * Called when node has changed.
     *
     * @param node the node
     * @param childIndices the child indices
     */
  public void nodesChanged(TreeNode node, int[] childIndices) {
    if (node != null) {
      if (childIndices != null) { // NOPMD
        int count = childIndices.length;
        if (count > 0) {
          Object[] children = new Object[count];
          for (int counter = 0; counter < count; counter++) {
            children[counter] = node.getChildAt(childIndices[counter]);
          }
          fireTreeNodesChanged(this, getPathToRoot(node), childIndices,
                               children);
        }
      } else if (node == root) { // NOPMD
        fireTreeNodesChanged(this, getPathToRoot(node), null, null);
      }
    }
  }

  /**
   * Returns path to the root for the specified node.
   *
   * @param node the node
   *
   * @return  path to the root for the specified node
   */
  public TreeNode[] getPathToRoot(TreeNode node) {
    return getPathToRoot(node, 0);
  }

// Protected Methods. ----------------------------------------------------------

  /**
   * Returns path to the root for the specified node.
   *
   * @param node the node
   * @param depth the depth
   *
   * @return  path to the root for the specified node
   */
  protected TreeNode[] getPathToRoot(TreeNode node, int depth) { // NOPMD
    TreeNode[] nodes;
    if (node == null) {
      if (depth == 0) {
        return EMPTY_TREE_NODE_ARRAY; // NOPMD
      }
      nodes = new TreeNode[depth];
    } else {
      depth++;
      if (node == root) { // NOPMD
        nodes = new TreeNode[depth];
      } else {
        nodes = getPathToRoot(node.getParent(), depth);
      }
      nodes[nodes.length - depth] = node;
    }
    return nodes;
  }

  /**
   * Fires tree nodes changed event.
   *
   * @param source the source node
   * @param path the path
   * @param childIndices the child indices
   * @param children the new children
   */
  protected void fireTreeNodesChanged(Object source, Object[] path,
                                      int[] childIndices, Object[] children) {
    int count = listenerList.getListenerCount(TreeModelListener.class);
    if (count > 0) {
      TreeModelListener[] listeners =
          (TreeModelListener[])listenerList.
                                     getListenersArray(TreeModelListener.class);
      final TreeModelEvent treeModelEvent = new TreeModelEvent(source, path,
                                                               childIndices,
                                                               children);
      for (int i = count; --i >= 0;) {
        listeners[i].treeNodesChanged(treeModelEvent);
      }
    }
  }

  /**
   * Fires tree structure changed event.
   *
   * @param source the source node
   * @param path the path
   */
  protected void fireTreeStructureChanged(Object source, TreePath path) {
    int count = listenerList.getListenerCount(TreeModelListener.class);
    if (count > 0) {
      TreeModelListener[] listeners =
          (TreeModelListener[])listenerList.
                                     getListenersArray(TreeModelListener.class);
      final TreeModelEvent treeModelEvent = new TreeModelEvent(source, path);
      for (int i = count; --i >= 0;) {
        listeners[i].treeStructureChanged(treeModelEvent);
      }
    }
  }

  /**
   * Fires tree structure changed event.
   *
   * @param source the source node
   * @param path the path
   * @param childIndices the child indices
   * @param children the new children
   */
  protected void fireTreeStructureChanged(Object source, Object[] path,
                                          int[] childIndices,
                                          Object[] children) {
    int count = listenerList.getListenerCount(TreeModelListener.class);
    if (count > 0) {
      TreeModelListener[] listeners =
          (TreeModelListener[])listenerList.
                                     getListenersArray(TreeModelListener.class);
      final TreeModelEvent treeModelEvent = new TreeModelEvent(source, path,
                                                               childIndices,
                                                               children);
      for (int i = count; --i >= 0;) {
        listeners[i].treeStructureChanged(treeModelEvent);
      }
    }
  }

  /**
   * Fires tree nodes removed event.
   *
   * @param source the source node
   * @param path the path
   * @param childIndices the child indices
   * @param children the new children
   */
  protected void fireTreeNodesInserted(Object source, Object[] path,
                                       int[] childIndices, Object[] children) {
    int count = listenerList.getListenerCount(TreeModelListener.class);
    if (count > 0) {
      TreeModelListener[] listeners =
          (TreeModelListener[])listenerList.
                                     getListenersArray(TreeModelListener.class);
      final TreeModelEvent treeModelEvent = new TreeModelEvent(source, path,
                                                               childIndices,
                                                               children);
      for (int i = count; --i >= 0;) {
        listeners[i].treeNodesInserted(treeModelEvent);
      }
    }
  }

 /**
  * Fires tree nodes inserted event.
  *
  * @param source the source node
  * @param path the path
  * @param childIndices the child indices
  * @param children the new children
  */
  protected void fireTreeNodesRemoved(Object source, Object[] path,
                                      int[] childIndices, Object[] children) {
    int count = listenerList.getListenerCount(TreeModelListener.class);
    if (count > 0) {
      TreeModelListener[] listeners =
          (TreeModelListener[])listenerList.
                                     getListenersArray(TreeModelListener.class);
      final TreeModelEvent treeModelEvent = new TreeModelEvent(source, path,
                                                               childIndices,
                                                               children);
      for (int i = count; --i >= 0;) {
        listeners[i].treeNodesRemoved(treeModelEvent);
      }
    }
  }

// TreeModel Implementation. ---------------------------------------------------

  /**
   * {@inheritDoc}
   */
  public Object getRoot() {
    return root;
  }

  /**
   * {@inheritDoc}
   */
  public boolean isLeaf(Object node) {
    if (node instanceof TreeNode) {
      if (asksAllowsChildren) {
        return !((TreeNode)node).getAllowsChildren();
      }
      return ((TreeNode)node).isLeaf();
    }
    return true;
  }

  /**
   * {@inheritDoc}
   */
  public int getChildCount(Object parent) {
    if (parent instanceof TreeNode) {
      return ((TreeNode)parent).getChildCount();
    }
    return 0;
  }

  /**
   * {@inheritDoc}
   */
  public Object getChild(Object parent, int index) {
    if (parent instanceof TreeNode) {
      return ((TreeNode)parent).getChildAt(index);
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public int getIndexOfChild(Object parent, Object child) {
    if ((parent instanceof TreeNode) && (child instanceof TreeNode)) {
      return ((TreeNode)parent).getIndex((TreeNode)child);
    }
    return 0;
  }

  /**
   * {@inheritDoc}
   */
  public void valueForPathChanged(TreePath path, Object value) {
    Object node = path.getLastPathComponent();
    if (node instanceof MutableTreeNode) {
      MutableTreeNode mutableNode = (MutableTreeNode)node;
      mutableNode.setUserObject(value);
      nodeChanged(mutableNode);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void addTreeModelListener(TreeModelListener listener) {
    listenerList.add(TreeModelListener.class, listener);
  }

  /**
   * {@inheritDoc}
   */
  public void removeTreeModelListener(TreeModelListener listener) {
    listenerList.remove(TreeModelListener.class, listener);
  }

}
